/* PeerConnectionIn - Handles incomming messages and hands them to PeerState.
   Copyright (C) 2003 Mark J. Wielaard

   This file is part of Snark.
   
   This program is free software; you can redistribute it and/or modify
   it under the terms of the GNU General Public License as published by
   the Free Software Foundation; either version 2, or (at your option)
   any later version.
 
   This program is distributed in the hope that it will be useful,
   but WITHOUT ANY WARRANTY; without even the implied warranty of
   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
   GNU General Public License for more details.
 
   You should have received a copy of the GNU General Public License
   along with this program; if not, write to the Free Software Foundation,
   Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
 */

package org.klomp.snark;

import java.io.*;
import java.net.*;
import java.util.*;

class PeerConnectionIn implements Runnable {
	private final Peer peer;
	private final DataInputStream din;

	private Thread thread;
	private boolean quit;

	public PeerConnectionIn(Peer peer, DataInputStream din) {
		this.peer = peer;
		this.din = din;
		quit = false;
	}

	void disconnect() {
		if(quit == true)
			return;

		quit = true;
		Thread t = thread;
		if(t != null)
			t.interrupt();
	}

	public void run() {
		thread = Thread.currentThread();
		try {
			PeerState ps = peer.state;
			while(!quit && ps != null) {
				// Common variables used for some messages.
				int piece;
				int begin;
				int len;

				// Wait till we hear something...
				// The length of a complete message in bytes.
				int i = din.readInt();
				if(i < 0)
					throw new IOException("Unexpected length prefix: " + i);

				if(i == 0) {
					ps.keepAliveMessage();
					continue;
				}

				byte b = din.readByte();
				Message m = new Message();
				m.type = b;
				switch(b) {
				case 0:
					ps.chokeMessage(true);
					break;
				case 1:
					ps.chokeMessage(false);
					break;
				case 2:
					ps.interestedMessage(true);
					break;
				case 3:
					ps.interestedMessage(false);
					break;
				case 4:
					piece = din.readInt();
					ps.haveMessage(piece);
					break;
				case 5:
					byte[] bitmap = new byte[i - 1];
					din.readFully(bitmap);
					ps.bitfieldMessage(bitmap);
					break;
				case 6:
					piece = din.readInt();
					begin = din.readInt();
					len = din.readInt();
					ps.requestMessage(piece, begin, len);
					break;
				case 7:
					piece = din.readInt();
					begin = din.readInt();
					len = i - 9;
					Request req = ps.getOutstandingRequest(piece, begin, len);
					byte[] piece_bytes;
					if(req != null) {
						piece_bytes = req.bs;
						din.readFully(piece_bytes, begin, len);
						ps.pieceMessage(req);
					} else {
						// XXX - Consume but throw away afterwards.
						piece_bytes = new byte[len];
						din.readFully(piece_bytes);
					}
					break;
				case 8:
					piece = din.readInt();
					begin = din.readInt();
					len = din.readInt();
					ps.cancelMessage(piece, begin, len);
					break;
				default:
					byte[] bs = new byte[i - 1];
					din.readFully(bs);
					ps.unknownMessage(b, bs);
				}
			}
		} catch(IOException ioe) {
			// Ignore, probably the other side closed connection.
		} catch(Throwable t) {
			Snark.debug(peer + ": " + t, Snark.ERROR);
			t.printStackTrace();
		} finally {
			peer.disconnect();
		}
	}
}
